# -*- coding: utf-8 -*-

"""
wssh.server

This module provides server capabilities of wssh
"""

import gevent
from gevent.socket import wait_read, wait_write
from gevent.select import select
from gevent.event import Event


import pexpect

import socket

try:
    import simplejson as json
except ImportError:
    import json

from StringIO import StringIO


#noinspection PyUnreachableCode
class WSSHBridge(object):
    """ WebSocket to SSH Bridge Server """

    def __init__(self, websocket):
        """ Initialize a WSSH Bridge

        The websocket must be the one created by gevent-websocket
        """
        self._websocket = websocket
        #self._ssh = paramiko.SSHClient()
        #self._ssh.set_missing_host_key_policy(
        #    paramiko.AutoAddPolicy())
        self._tasks = []

    def open(self,username, hostname, password,port='22'):
    	"""This runs a command on the remote host. This could also be done with the
	pxssh class, but this demonstrates what that class does at a simpler level.
	This returns a pexpect.spawn object. This handles the case when you try to
	connect to a new host and ssh asks you if you want to accept the public key
	fingerprint and continue connecting. """

        ssh_newkey = 'Are you sure you want to continue connecting'
        self._child = pexpect.spawn('ssh  -o "RSAAuthentication=no" -o "PubkeyAuthentication=no" -p %s -l %s %s '%(port,username, hostname))


        i = self._child.expect([pexpect.TIMEOUT, 'No route to host', ssh_newkey, 'password: '])
        if i == 0:
            print 'ERROR!'
            print 'SSH could not login. Here is what SSH said:'
            print self._child.before, self._child.after
            return None
        if i == 1:
            print 'Host is unreachable,maybe server is down '
            print 'SSH could not login. Here is what SSH said:'
            print self._child.before, self._child.after
            return None

        if i == 2: # SSH does not have the public key. Just accept it.
            self._child.sendline ('yes')
            print self._child.before, self._child.after
            i = self._child.expect([pexpect.TIMEOUT, 'password: '])
            if i == 0: # Timeout
                print 'ERROR!'
                print 'SSH could not login. Here is what SSH said:'
                print self._child.before, self._child.after
                return None

        self._child.sendline(password)
        p=self._child.expect(['password:', '$'])
        if p == 0:
            print 'password wrong'
            return None
        if p == 1:
	        pass

    #def open(self, hostname, port=22, username=None, password=None,
    #                private_key=None, key_passphrase=None,
    #                allow_agent=False, timeout=None):
    #    """ Open a connection to a remote SSH server
    #
    #    In order to connect, either one of these credentials must be
    #    supplied:
    #        * Password
    #            Password-based authentication
    #        * Private Key
    #            Authenticate using SSH Keys.
    #            If the private key is encrypted, it will attempt to
    #            load it using the passphrase
    #        * Agent
    #            Authenticate using the *local* SSH agent. This is the
    #            one running alongside wsshd on the server side.
    #    """
    #    try:
    #        pkey = None
    #        if private_key:
    #            pkey = self._load_private_key(private_key, key_passphrase)
    #        self._ssh.connect(
    #            hostname=hostname,
    #            port=port,
    #            username=username,
    #            password=password,
    #            pkey=pkey,
    #            timeout=timeout,
    #            allow_agent=allow_agent,
    #            look_for_keys=False)
    #    except socket.gaierror as e:
    #        self._websocket.send(json.dumps({'error':
    #            'Could not resolve hostname {0}: {1}'.format(
    #                hostname, e.args[1])}))
    #        raise
    #    except Exception as e:
    #        self._websocket.send(json.dumps({'error': e.message or str(e)}))
    #        raise

    def _forward_inbound(self, channel):
        """ Forward inbound traffic (websockets -> ssh) """
        try:
            while True:
                data = self._websocket.receive()
                if not data:
                    return
                data = json.loads(str(data))
                if 'resize' in data:
                    channel.resize_pty(
                        data['resize'].get('width', 80),
                        data['resize'].get('height', 24))
                if 'data' in data:
                    channel.send(data['data'])
        finally:
            self.close()

    def _forward_outbound(self, channel):
        """ Forward outbound traffic (ssh -> websockets) """
        try:
            while True:
                wait_read(channel.fileno())
                data = channel.read_nonblocking(size=1024)
                if not len(data):
                    return
                self._websocket.send(json.dumps({'data': data}))
        finally:
            self.close()

    def _bridge(self, channel):
        """ Full-duplex bridge between a websocket and a SSH channel """
        #channel.setblocking(False)
        #channel.read_nonblocking()
        #channel.settimeout(0.0)
        self._tasks = [
            gevent.spawn(self._forward_inbound, channel),
            gevent.spawn(self._forward_outbound, channel)
        ]
        gevent.joinall(self._tasks)

    def close(self):
        """ Terminate a bridge session """
        gevent.killall(self._tasks, block=True)
        self._tasks = []
        self._ssh.close()

    def execute(self, command, term='xterm'):
        """ Execute a command on the remote server

        This method will forward traffic from the websocket to the SSH server
        and the other way around.

        You must connect to a SSH server using ssh_connect()
        prior to starting the session.
        """
        transport = self._ssh.get_transport()
        channel = transport.open_session()
        channel.get_pty(term)
        channel.exec_command(command)
        self._bridge(channel)
        channel.close()

    def shell(self, term='xterm'):
        """ Start an interactive shell session

        This method invokes a shell on the remote SSH server and proxies
        traffic to/from both peers.

        You must connect to a SSH server using ssh_connect()
        prior to starting the session.
        """
        channel = self._child
        self._bridge(channel)
        channel.close()
